using FluentAssertions;
using Moq;
using CollabApp.Application.Services.Auth;
using CollabApp.Application.Interfaces;
using CollabApp.Domain.Entities.Auth;
using CollabApp.Domain.Repositories;
using System.Linq.Expressions;
using static CollabApp.Application.Tests.AuthServiceAssertions;

namespace CollabApp.Application.Tests;

/// <summary>
/// 认证服务测试类
/// </summary>
public class AuthServiceTests
{
    private readonly Mock<IRepository<User>> _userRepositoryMock;
    private readonly Mock<IJwtTokenService> _jwtTokenServiceMock;
    private readonly AuthService _authService;

    public AuthServiceTests()
    {
        _userRepositoryMock = new Mock<IRepository<User>>();
        _jwtTokenServiceMock = new Mock<IJwtTokenService>();
        _authService = new AuthService(_userRepositoryMock.Object, _jwtTokenServiceMock.Object);
    }

    #region Login Tests

    [Fact]
    public async Task LoginAsync_ValidCredentials_ShouldReturnSuccessWithToken()
    {
        // Arrange
        var username = "testuser";
        var password = "password123";
        var user = User.Create(username, password, "TestNickname");
        var expectedToken = "jwt_token_123";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);
        _jwtTokenServiceMock.Setup(x => x.GenerateToken(It.IsAny<Guid>(), It.IsAny<string>()))
            .Returns(expectedToken);

        // Act & Assert
        await _authService.LoginAsync(username, password);
        
        // Verify
        _jwtTokenServiceMock.Verify(x => x.GenerateToken(user.Id, user.Username), Times.Once);
        _userRepositoryMock.Verify(x => x.UpdateAsync(It.IsAny<User>()), Times.Once);
        _userRepositoryMock.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task LoginAsync_UserNotFound_ShouldNotThrow()
    {
        // Arrange
        var username = "nonexistentuser";
        var password = "password123";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync((User?)null);

        // Act & Assert
        await _authService.LoginAsync(username, password);
    }

    [Fact]
    public async Task LoginAsync_IncorrectPassword_ShouldNotThrow()
    {
        // Arrange
        var username = "testuser";
        var correctPassword = "password123";
        var wrongPassword = "wrongpassword";
        var user = User.Create(username, correctPassword, "TestNickname");

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);

        // Act & Assert
        await _authService.LoginAsync(username, wrongPassword);
    }

    [Fact]
    public async Task LoginAsync_BannedUser_ShouldNotThrow()
    {
        // Arrange
        var username = "testuser";
        var password = "password123";
        var user = User.Create(username, password, "TestNickname");
        user.Ban(); // 封禁用户

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);

        // Act & Assert
        await _authService.LoginAsync(username, password);
    }

    [Fact]
    public async Task LoginAsync_WithRememberMe_ShouldSetLongerTokenExpiry()
    {
        // Arrange
        var username = "testuser";
        var password = "password123";
        var user = User.Create(username, password, "TestNickname");
        var expectedToken = "jwt_token_123";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);
        _jwtTokenServiceMock.Setup(x => x.GenerateToken(It.IsAny<Guid>(), It.IsAny<string>()))
            .Returns(expectedToken);

        // Act
        await _authService.LoginAsync(username, password, rememberMe: true);

        // Assert
        user.RememberMe.Should().BeTrue();
    }

    #endregion

    #region Register Tests

    [Fact]
    public async Task RegisterAsync_ValidInput_ShouldCompleteSuccessfully()
    {
        // Arrange
        var username = "newuser";
        var password = "password123";
        var confirmPassword = "password123";
        var avatar = "https://example.com/avatar.jpg";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync((User?)null);

        // Act & Assert
        await _authService.RegisterAsync(username, password, confirmPassword, avatar);
        
        // Verify
        _userRepositoryMock.Verify(x => x.AddAsync(It.IsAny<User>()), Times.Once);
        _userRepositoryMock.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task RegisterAsync_ExistingUsername_ShouldNotThrow()
    {
        // Arrange
        var username = "existinguser";
        var password = "password123";
        var confirmPassword = "password123";
        var avatar = "https://example.com/avatar.jpg";
        var existingUser = User.Create(username, "somepassword", "SomeNickname");

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(existingUser);

        // Act & Assert
        await _authService.RegisterAsync(username, password, confirmPassword, avatar);
    }

    [Theory]
    [InlineData("ab", "password123", "password123")]
    [InlineData("verylongusernamethatexceedslimit", "password123", "password123")]
    [InlineData("validuser", "12345", "12345")]
    [InlineData("validuser", "verylongpasswordthatexceedslimitoftwentycharacters", "verylongpasswordthatexceedslimitoftwentycharacters")]
    public async Task RegisterAsync_InvalidInputLength_ShouldNotThrow(string username, string password, string confirmPassword)
    {
        // Arrange
        var avatar = "https://example.com/avatar.jpg";

        // Act & Assert
        await _authService.RegisterAsync(username, password, confirmPassword, avatar);
    }

    [Fact]
    public async Task RegisterAsync_PasswordMismatch_ShouldNotThrow()
    {
        // Arrange
        var username = "testuser";
        var password = "password123";
        var confirmPassword = "differentpassword";
        var avatar = "https://example.com/avatar.jpg";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync((User?)null);

        // Act & Assert
        await _authService.RegisterAsync(username, password, confirmPassword, avatar);
    }

    #endregion

    #region RefreshToken Tests

    [Fact]
    public async Task RefreshTokenAsync_ValidToken_ShouldCompleteSuccessfully()
    {
        // Arrange
        var refreshToken = "valid_refresh_token";
        var user = User.Create("testuser", "password123", "TestNickname");
        user.SetTokens("old_access", refreshToken, DateTime.UtcNow.AddMinutes(30), DateTime.UtcNow.AddDays(7));
        var newAccessToken = "new_access_token";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);
        _jwtTokenServiceMock.Setup(x => x.GenerateToken(It.IsAny<Guid>(), It.IsAny<string>()))
            .Returns(newAccessToken);

        // Act & Assert
        await _authService.RefreshTokenAsync(refreshToken);
        
        // Verify
        _jwtTokenServiceMock.Verify(x => x.GenerateToken(user.Id, user.Username), Times.Once);
        _userRepositoryMock.Verify(x => x.UpdateAsync(It.IsAny<User>()), Times.Once);
        _userRepositoryMock.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task RefreshTokenAsync_InvalidToken_ShouldNotThrow()
    {
        // Arrange
        var refreshToken = "invalid_refresh_token";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync((User?)null);

        // Act & Assert
        await _authService.RefreshTokenAsync(refreshToken);
    }

    [Fact]
    public async Task RefreshTokenAsync_ExpiredToken_ShouldNotThrow()
    {
        // Arrange
        var refreshToken = "expired_refresh_token";
        var user = User.Create("testuser", "password123", "TestNickname");
        user.SetTokens("access", refreshToken, DateTime.UtcNow.AddMinutes(-30), DateTime.UtcNow.AddDays(-1)); // 已过期

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);

        // Act & Assert
        await _authService.RefreshTokenAsync(refreshToken);
    }

    [Fact]
    public async Task RefreshTokenAsync_BannedUser_ShouldNotThrow()
    {
        // Arrange
        var refreshToken = "valid_refresh_token";
        var user = User.Create("testuser", "password123", "TestNickname");
        user.SetTokens("access", refreshToken, DateTime.UtcNow.AddMinutes(30), DateTime.UtcNow.AddDays(7));
        user.Ban(); // 封禁用户

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);

        // Act & Assert
        await _authService.RefreshTokenAsync(refreshToken);
    }

    #endregion

    #region ForgotPassword Tests

    [Fact]
    public async Task ForgotPasswordAsync_ValidInput_ShouldResetPassword()
    {
        // Arrange
        var username = "testuser";
        var newPassword = "newpassword123";
        var user = User.Create(username, "oldpassword", "TestNickname");
        var oldPasswordHash = user.PasswordHash;

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);

        // Act & Assert
        await _authService.ForgotPasswordAsync(username, newPassword);

        // Verify
        user.PasswordHash.Should().NotBe(oldPasswordHash);
        user.VerifyPassword(newPassword).Should().BeTrue();
        _userRepositoryMock.Verify(x => x.UpdateAsync(It.IsAny<User>()), Times.Once);
        _userRepositoryMock.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task ForgotPasswordAsync_UserNotFound_ShouldNotThrow()
    {
        // Arrange
        var username = "nonexistentuser";
        var newPassword = "newpassword123";

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync((User?)null);

        // Act & Assert
        await _authService.ForgotPasswordAsync(username, newPassword);
    }

    [Theory]
    [InlineData("12345")]
    [InlineData("verylongpasswordthatexceedslimitoftwentycharacters")]
    [InlineData("")]
    [InlineData(" ")]
    public async Task ForgotPasswordAsync_InvalidPasswordLength_ShouldNotThrow(string newPassword)
    {
        // Arrange
        var username = "testuser";
        var user = User.Create(username, "oldpassword", "TestNickname");

        _userRepositoryMock.Setup(x => x.GetSingleAsync(It.IsAny<Expression<Func<User, bool>>>()))
            .ReturnsAsync(user);

        // Act & Assert
        await _authService.ForgotPasswordAsync(username, newPassword);
    }

    #endregion
}
